using System;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Direct3D9;

namespace TileGert
{
  /// <summary>
  /// Implements a renderer that uses Direct3D 9 to render the Gert game.
  /// </summary>
  public class GertRenderer
  {
    //SlimDX
    private Device _d3dDevice = null;
    private Sprite _sprite = null;

    //Gert specific
    private GertGame _game = null;
    private GertColor _foregroundColor = GertColor.White;

    //Default .NET
    private Control _canvas = null;
    private int _screenWidth = 640;
    private int _screenHeight = 480;
    private bool _fullscreen = false;
    private bool _antialias = false;

    /// <summary>
    /// Creates a new renderer for the game.
    /// </summary>
    /// <param name="game">The game the renderer is used for.</param>
    public GertRenderer(GertGame game)
    {
      _game = game;
    }

    /// <summary>
    /// Gets or sets the control the game is drawn to.
    /// If it is null after the BeforeInit method was called,
    /// a new Form is created.
    /// </summary>
    public Control Canvas
    {
      get { return _canvas; }
      set { _canvas = value; }
    }

    /// <summary>
    /// Gets or sets the visibility of the given canvas.
    /// </summary>
    public bool CanvasVisible
    {
      get { return _canvas == null ? false : _canvas.Visible; }
      set 
      {
        if (_canvas != null)
          _canvas.Visible = value;
      }
    }

    /// <summary>
    /// Gets a value that determines if the canvas was created yet.
    /// </summary>
    public bool CanvasCreated
    {
      get { return _canvas == null ? false : _canvas.Created; }
    }

    /// <summary>
    /// Gets or sets the height of the screen in pixels. Default is 480.
    /// </summary>
    public int ScreenHeight
    {
      get { return _screenHeight; }
      set { _screenHeight = value; }
    }

    /// <summary>
    /// Gets or sets the width of the screen in pixels. Default is 640.
    /// </summary>
    public int ScreenWidth
    {
      get { return _screenWidth; }
      set { _screenWidth = value; }
    }

    /// <summary>
    /// Gets or sets a value that determines if the game runs in fullscreen (true) or windowed (false) mode. Default is false.
    /// </summary>
    public bool FullScreen
    {
      get { return _fullscreen; }
      set { _fullscreen = value; }
    }
    
    /// <summary>
    /// Gets or sets a value that determines if polygons are antialiased (true) or not (false). Default is false.
    /// </summary>
    public bool AntiAliasing
    {
      get { return _antialias; }
      set { _antialias = value; }
    }

    /// <summary>
    /// Gets or sets the color polygons are drawn with.
    /// </summary>
    public GertColor ForegroundColor
    {
      get { return _foregroundColor; }
      set { _foregroundColor = value; }
    }

    /// <summary>
    /// Gets the Direct3D device that is used to draw the scene.
    /// </summary>
    public Device D3DDevice
    {
      get { return _d3dDevice; }
    }

    /// <summary>
    /// Gets the Direct3D sprite that is used to draw the sprites.
    /// </summary>
    public Sprite D3DSprite
    {
      get { return _sprite; }
    }

    /// <summary>
    /// Initializes the renderer. All paramaters like screen dimensions, fullscreen
    /// and canvas have to be set up to here or the defaults will be used.
    /// If the initialisation fails (false is returned) see the log for info about the error.
    /// </summary>
    /// <returns>True if the initialsation was successfull, else false.</returns>
    public bool Initialize()
    {
      if (_canvas == null)
      {
        Form form = new Form();
        form.ClientSize = new System.Drawing.Size(_screenWidth, _screenHeight);
        form.MaximizeBox = false;
        form.MinimizeBox = false;
        form.FormBorderStyle = _fullscreen ? FormBorderStyle.None : FormBorderStyle.FixedSingle;
        
        _canvas = form;
      }

      PresentParameters pp = new PresentParameters();
      pp.BackBufferWidth = _screenWidth;
      pp.BackBufferHeight = _screenHeight;
      pp.BackBufferCount = 2;
      pp.DeviceWindowHandle = _canvas.Handle;
      pp.Windowed = !_fullscreen;
      pp.PresentationInterval = PresentInterval.Immediate;
      pp.EnableAutoDepthStencil = false;
      
      //Handle antialiasing
      pp.SwapEffect = _antialias ? SwapEffect.Discard : SwapEffect.Flip;
      pp.Multisample = _antialias ? MultisampleType.FourSamples : MultisampleType.None;
            
      try
      {
        _d3dDevice = new Device(new Direct3D(), 0, DeviceType.Hardware, _canvas.Handle, CreateFlags.SoftwareVertexProcessing, pp);
        
        _sprite = new Sprite(_d3dDevice);
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while creating the Direct3D device: " + ex.Message, 0);
        return false;
      }
    }

    /// <summary>
    /// Finalizes the renderer when it is not needed anymore.
    /// </summary>
    /// <returns>True if the finalisation was successfull, else false.</returns>
    public bool Finalize()
    {
      //TODO: Muss hier noch was mit dem device gemacht werden?
      return true;
    }

    /// <summary>
    /// Clears the whole screen with the given color.
    /// </summary>
    /// <param name="color">The color the screen is cleared with.</param>
    /// <returns>True if the screen was cleared successfull, else false.</returns>
    public bool Clear(GertColor color)
    {
      try
      {
        _d3dDevice.Clear(ClearFlags.Target, color.ToColor4(), 1.0f, 1);
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while clearing the screen: " + ex.Message, 0);
        return false;
      }
    }

    /// <summary>
    /// Begin has to be called before a call one of the "Draw..." methods. It sets the renderer to a state
    /// the game can be drawn to the screen.
    /// </summary>
    /// <returns>True if the method was successfull, else false.</returns>
    public bool Begin()
    {
      try
      {
        _d3dDevice.BeginScene();
        _sprite.Begin(SpriteFlags.AlphaBlend);
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while beginning the scene: " + ex.Message, 0);
        return false;
      }
    }

    /// <summary>
    /// When done drawing things with the "Draw..." methods End() has to be called for the
    /// final image to be displayed on screen.
    /// </summary>
    /// <returns>True if the final image was successfully displayed, else false.</returns>
    public bool End()
    {
      try
      {
        _sprite.End();
        _d3dDevice.EndScene();
        _d3dDevice.Present();
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while ending the scene: " + ex.Message, 0);
        return false;
      }
    }

    #region Sprite drawing

    /// <summary>
    /// Draws a sprite with the given name to the given screen coordinates.
    /// </summary>
    /// <param name="name">The name of the sprite.</param>
    /// <param name="x">The screens x coordinate.</param>
    /// <param name="y">The screens y coordinate.</param>
    /// <returns>True if the sprite was successfully drawn, else false.</returns>
    public bool DrawSprite(string name, int x, int y)
    {
      return DrawSprite(name, x, y, _foregroundColor);
    }

    /// <summary>
    /// Draws the given sprite to the screen if it is visible.
    /// </summary>
    /// <param name="sprite">The sprite to be drawn.</param>
    /// <returns>True if the sprite was successfully drawn, else false.</returns>
    public bool DrawSprite(GertSprite sprite)
    {
      return DrawSprite(sprite.Image, sprite.Position.X, sprite.Position.Y, _foregroundColor);
    }

    /// <summary>
    /// Draws the given sprite to the screena if it is visible.
    /// The sprite also is tinted with the given color instead of the <code>ForegroundColor</code>.
    /// </summary>
    /// <param name="sprite">The sprite to be drawn.</param>
    /// <param name="color">The tint color. This overrides <code>ForegroundColor</code></param>
    /// <returns>True if the sprite was successfully drawn, else false.</returns>
    public bool DrawSprite(GertSprite sprite, GertColor color)
    {
      return DrawSprite(sprite.Image, sprite.Position.X, sprite.Position.Y, color);
    }

    /// <summary>
    /// Draws a sprite with the given name to the given screen coordinates and tints it with the given color.
    /// </summary>
    /// <param name="name">The name of the sprite.</param>
    /// <param name="x">The screens x coordinate.</param>
    /// <param name="y">The screens y coordinate.</param>
    /// <param name="color">The tint color.</param>
    /// <returns>True if the sprite was successfully drawn, else false.</returns>
    public bool DrawSprite(string name, float x, float y, GertColor color)
    {
      try
      {
        Texture tex = _game.Content.GetAsset<Texture>(name);
        if (tex != null)
          _sprite.Draw(tex, Vector3.Zero, new Vector3(x, y, 0), color.ToColor4());
        else
          throw new Exception("The sprite \"" + name + "\" could not be found!");
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while drawing the sprite \"" + name + "\": " + ex.Message, 1);
        return false;
      }
    }

    /// <summary>
    /// Draw a 3 by 3 pixel sprite ("tilegert_3x3") centered at the position of the given point.
    /// </summary>
    /// <param name="point">The point where the sprite is drawn.</param>
    /// <returns>True if the point was successfully drawn, else false.</returns>
    public bool DrawPoint(GertPoint point)
    {
      return DrawSprite("tilegert_3x3", point.X - 1, point.Y - 1, _foregroundColor);
    }

    /// <summary>
    /// Draw a 3 by 3 pixel sprite ("tilegert_3x3") centered at the position of the given point tinted by the given color.
    /// </summary>
    /// <param name="point">The point where the sprite is drawn.</param>
    /// <param name="color">The color of the point.</param>
    /// <returns>True if the point was successfully drawn, else false.</returns>
    public bool DrawPoint(GertPoint point, GertColor color)
    {
      return DrawSprite("tilegert_3x3", point.X - 1, point.Y - 1, color);
    }

    #endregion

    #region Polyline drawing

    private bool DrawPrimitive(PrimitiveType type, int primitveCount, GertTransformedColoredVertex[] vertices)
    {
      try
      {
        _d3dDevice.VertexFormat = VertexFormat.PositionRhw | VertexFormat.Diffuse; //Has to be done here because it seems to be reset each frame
        _d3dDevice.DrawUserPrimitives<GertTransformedColoredVertex>(type, primitveCount, vertices);
        return true;
      }
      catch (Exception ex)
      {
        _game.Logger.LogLine("Error while drawing primitive: " + ex.Message, 1);
        return false;
      }
    }

    /// <summary>
    /// Renders a single polygon line starting at (x1, y1) and ending at (x2, y2).
    /// </summary>
    /// <param name="x1">The x coordinate of the start point.</param>
    /// <param name="y1">The y coordinate of the start point.</param>
    /// <param name="x2">The x coordinate of the end point.</param>
    /// <param name="y2">The y coordinate of the end point.</param>
    /// <returns>True if the line was successfullly drawn, else False.</returns>
    public bool DrawLine(float x1, float y1, float x2, float y2)
    {
      GertPoint p1 = new GertPoint(x1, y1);
      GertPoint p2 = new GertPoint(x2, y2);
      return DrawLine(p1, p2);
    }
    
    /// <summary>
    /// Renders a single polygon line starting at (p1) and ending at (p2).
    /// </summary>
    /// <param name="p1">The start point.</param>
    /// <param name="p2">The end point.</param>
    /// <returns>True if the line was successfullly drawn, else False.</returns>
    public bool DrawLine(GertPoint p1, GertPoint p2)
    {
      GertTransformedColoredVertex[] data = new GertTransformedColoredVertex[2];
      int fgColor = _foregroundColor.ToInt();
      data[0] = p1.AsVertex;
      data[0].Color = fgColor;
      data[1] = p2.AsVertex;
      data[1].Color = fgColor;

      return DrawPrimitive(PrimitiveType.LineList, 1, data);
    }
   
    /// <summary>
    /// Renders a single polygon line starting at (x1, y1) and ending at (x2, y2).
    /// </summary>
    /// <param name="x1">The horizontal position of the start point.</param>
    /// <param name="y1">The vertical position of the start point.</param>
    /// <param name="c1">The color of the start point.</param>
    /// <param name="x2">The horizontal position of the end point.</param>
    /// <param name="y2">The vertical position of the end point.</param>
    /// <param name="c2">The color of the end point.</param>
    /// <returns>True if the line was successfully drawn, else false.</returns>
    public bool DrawLine(float x1, float y1, GertColor c1, float x2, float y2, GertColor c2)
    {
      GertTransformedColoredVertex[] data = new GertTransformedColoredVertex[2];
      data[0].Position.X = x1;
      data[0].Position.Y = y1;
      data[0].Color = c1.ToInt();
      data[1].Position.X = x2;
      data[1].Position.Y = y2;
      data[1].Color = c2.ToInt();

      return DrawPrimitive(PrimitiveType.LineList, 1, data);
    }

    /// <summary>
    /// Draw the given line segment as a poly line starting at Point1 and ending at Point2.
    /// </summary>
    /// <param name="segment">The line segment to be drawn.</param>
    /// <returns>True if the line was successfully drawn, else false.</returns>
    public bool DrawLine(GertLineSegment segment)
    {
      GertTransformedColoredVertex[] data = new GertTransformedColoredVertex[2];
      int fgColor = _foregroundColor.ToInt();
      data[0] = segment.Point1.AsVertex;
      data[0].Color = fgColor;
      data[1] = segment.Point2.AsVertex;
      data[1].Color = fgColor;

      return DrawPrimitive(PrimitiveType.LineList, 1, data);
    }

    /// <summary>
    /// Draws a non-closed polygon line starting at points[0] and ending at points[n] where n is the length of the array.
    /// Use this method if the vertices of the line are updated very often. If the line is static use GertPolygon instead,
    /// because it manages the points more efficiently.
    /// </summary>
    /// <param name="points">The points of the polyline.</param>
    /// <returns>True if the line was successfullly drawn, else False.</returns>
    public bool DrawPolyline(GertPoint[] points)
    {
      GertTransformedColoredVertex[] data = new GertTransformedColoredVertex[points.Length];
      int fgColor = _foregroundColor.ToInt();
      for (int i = 0; i < points.Length; i++)
      {
        data[i] = points[i].AsVertex;
        data[i].Color = fgColor;
      }

      return DrawPrimitive(PrimitiveType.LineStrip, data.Length - 1, data);
    }

    //TODO: Implement DrawPolygon with GertPolygon

    /// <summary>
    /// Draws the given rectangle as a poly line.
    /// </summary>
    /// <param name="rectangle">The rectangle to be drawn.</param>
    /// <returns>True if the rectangle was successfullly drawn, else False.</returns>
    public bool DrawRectangle(GertRectangle rectangle)
    {
      GertTransformedColoredVertex[] data = new GertTransformedColoredVertex[5];
      int fgColor = _foregroundColor.ToInt();

      data[0] = rectangle.LeftTop.AsVertex;
      data[0].Color = fgColor;
      data[1] = new GertPoint(rectangle.RightBottom.X, rectangle.LeftTop.Y).AsVertex;
      data[1].Color = fgColor;
      data[2] = rectangle.RightBottom.AsVertex;
      data[2].Color = fgColor;
      data[3] = new GertPoint(rectangle.LeftTop.X, rectangle.RightBottom.Y).AsVertex;
      data[3].Color = fgColor;
      data[4] = rectangle.LeftTop.AsVertex;
      data[4].Color = fgColor;

      return DrawPrimitive(PrimitiveType.LineStrip, 4, data);
    }

    /// <summary>
    /// Draws the given circle as a polyline.
    /// </summary>
    /// <param name="circle">The circle to be drawn.</param>
    /// <returns>True if the circle was successfullly drawn, else False.</returns>
    public bool DrawCircle(GertCircle circle)
    {
      GertTransformedColoredVertex[] data = circle.GetVertices(_foregroundColor);
      return DrawPrimitive(PrimitiveType.LineStrip, data.Length - 1, data);
    }

    #endregion

  }

}
